﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Afru;

namespace Afru
{
    public class Sprite : Component
    {
        #region Variables

        Texture2D _sprite;
        string _spritePath;
        Vector2 _origin;
        Vector2 _position;
        Vector2 _scale;
        float _angle;
        Vector2 _parallax = new Vector2(1);
        bool _hasParallax;

        public Vector2 Position { get { return _position; } set { _position = value; } }
        public float Angle { get { return _angle; } set { _angle = value; } }
        public Vector2 Scale { get { return _scale; } set { _scale = value; } }
        public bool HasParallax { get { return _hasParallax; } set { _hasParallax = value; } }
        protected Texture2D sprite { get { return _sprite; } set { _sprite = value; } }

        /// <summary>
        /// The point in which to rotate the sprite around 
        /// </summary>
        public Vector2 Origin { get { return _origin; } set { _origin = value; } }
        /// <summary>
        /// The parallax value to take into consideration when returning the camera matrix
        /// </summary>
        public Vector2 Parallax { get { return _parallax; } set { _parallax = value;} }

        public int Width { get { return _sprite.Width; } }
        public int Height { get {return _sprite.Height;} }

        public string SpritePath { get { return _spritePath; } set { _spritePath = value; _sprite = ParentEngine.Content.Load<Texture2D>(_spritePath); } }
                
        #endregion

        #region Constructor

        public Sprite(string TexturePath, Vector2 InitialPosition)
        {
            _spritePath = TexturePath;
            _position = InitialPosition;
            _angle = 0.0f;
            _scale = new Vector2(1.0f);
            _hasParallax = false;

            // This component doesn't update so we might as well set it to false
            this.Active = false;
        }

        #endregion

        #region Class functions

        /// <summary>
        /// This loads a new image into the sprite texture
        /// </summary>
        /// <param name="SpritePath">The path of the new image to load</param>
        public void SetSprite(string SpritePath)
        {
            _spritePath = SpritePath;
            _sprite = null;
            _sprite = ParentEngine.Content.Load<Texture2D>(_spritePath);
        }

        /// <summary>
        /// Set the angle of the sprite using degrees instead of radians
        /// </summary>
        /// <param name="AngleInDegrees">The angle to rotate by in degrees</param>
        public void SetAngleInDegrees(float AngleInDegrees)
        {
            _angle = MathHelper.ToRadians(AngleInDegrees);
        }

        /// <summary>
        /// Set the scale to 1 and the rotation to 0
        /// </summary>
        public void DefaultScaleRotation()
        {
            _scale = new Vector2(1.0f);
            _angle = 0.0f;
        }

        /// <summary>
        /// Checks to see if there is a collision with another Sprite using their bounding boxes
        /// </summary>
        /// <param name="sprite">The sprite to check against</param>
        /// <returns>True is there is a collision, false otherwise</returns>
        public bool BoundingBoxCollision(Sprite sprite)
        {
            // The two sprites haven't been transformed, its faster to just do normal rectangle collision
            if (_angle == 0 && sprite.Angle == 0)
            {
                Rectangle rec1, rec2;

                rec1 = new Rectangle((int)_position.X, (int)_position.Y, Width, Height);
                rec2 = new Rectangle((int)sprite.Position.X, (int)sprite.Position.Y, sprite.Width, sprite.Height);

                if (rec1.Intersects(rec2))
                    return true;
                else
                    return false;
            }
            else // At least one of the sprites has been transformed, do the check using their RotatedRectangle members
            {
                RotatedRectangle tmpBounds1, tmpBounds2;

                tmpBounds1 = this.GetBoundingBox();
                tmpBounds2 = sprite.GetBoundingBox();

                if (tmpBounds1.Intersects(tmpBounds2))
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Checks to see if there is a collision with a rotated rectangle
        /// </summary>
        /// <param name="rectangle">The rectangle to check against</param>
        /// <returns>True is there is a collision, false otherwise</returns>
        public bool BoundingBoxCollision(RotatedRectangle rectangle)
        {           
            RotatedRectangle tmpBounds;

            tmpBounds = this.GetBoundingBox();

            if (tmpBounds.Intersects(rectangle))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Looks for a collision between two sprites using their pixel colour values, less efficient than BoundingBoxCollision but more accurate
        /// </summary>
        /// <param name="sprite">The sprite to check against</param>
        /// <returns></returns>
        public bool PixelCollision(Sprite sprite)
        {
            // The color data for the two sprites
                Color[] tmpTexData1 = new Color[_sprite.Width * _sprite.Height]; // This instance of the sprite
                Color[] tmpTexData2 = new Color[sprite.Width * sprite.Height];   // The sprite we are checking against

            // Fill the tmpTexData arrays with the texture information
            _sprite.GetData(tmpTexData1);
            sprite.GetTexture().GetData(tmpTexData2);

            if (_angle == 0 && sprite.Angle == 0) // The sprites haven't been rotated, so we might as well ignore the matrix calculations
            {
                // Make sure that there is at least a collision between the two sprites, because pixel collision is very expensive
                if (this.BoundingBoxCollision(sprite))
                {
                    if (IntersectPixels(this.GetBoundingBox().CollisionRectangle, tmpTexData1, sprite.GetBoundingBox().CollisionRectangle, tmpTexData2))
                        return true;
                    else
                        return false;
                }
                else
                    return false;
            }
            else // One or both of the sprites have been transformed, do all calculations
            {
                // The matrices used to calculate the transformations 
                Matrix sprite1Transformations; // This instance of the sprite
                Matrix sprite2Transformations; // The sprite to check against

                sprite1Transformations =
                    Matrix.CreateTranslation(new Vector3(-_origin, 1.0f)) *
                    Matrix.CreateScale(_scale.X, _scale.Y, 1) *
                    Matrix.CreateRotationZ(_angle) *
                    Matrix.CreateTranslation(new Vector3(_position, 1.0f));

                sprite2Transformations =
                    Matrix.CreateTranslation(new Vector3(-sprite.Origin, 1.0f)) *
                    Matrix.CreateScale(sprite.Scale.X, sprite.Scale.Y, 1) *
                    Matrix.CreateRotationZ(sprite.Angle) *
                    Matrix.CreateTranslation(new Vector3(sprite.Position, 1.0f));
                    
                // Make sure that there is at least a collision between the two sprites, because pixel collision is very expensive
                if (this.GetBoundingBox().Intersects(sprite.GetBoundingBox()))
                {
                    if (IntersectPixels(sprite1Transformations, this.Width, this.Height, tmpTexData1, sprite2Transformations, sprite.Width, sprite.Height, tmpTexData2))
                        return true;
                    else
                        return false;
                }
                else
                    return false;            
            }
        }

        /// <summary>
        /// Looks for a collision with a texture using pixel colour values, less efficient than BoundingBoxCollision but more accurate
        /// </summary>
        /// <param name="texture">The Texture to check against</param>
        /// <param name="texturePosition">The position of the </param>
        /// <param name="textureAngle">The angle that the texture is rotated by</param>
        /// <returns></returns>
        public bool PixelCollision(Texture2D texture, Vector2 texturePosition, float textureAngle, Vector2 textureScale)
        {
            // The color data for the two sprites
            Color[] tmpTexData1 = new Color[_sprite.Width * _sprite.Height]; // This instance of the sprite
            Color[] tmpTexData2 = new Color[texture.Width * texture.Height];   // The sprite we are checking against

            // Fill the tmpTexData arrays with the texture information
            _sprite.GetData(tmpTexData1);
            texture.GetData(tmpTexData2);

            if (_angle == 0 && textureAngle == 0) // The sprites haven't been rotated, so we might as well ignore the matrix calculations
            {
                // Make sure that there is at least a collision between the two sprites, because pixel collision is very expensive
                if (this.BoundingBoxCollision(new RotatedRectangle(new Rectangle((int)texturePosition.X, (int)texturePosition.Y, texture.Width, texture.Height), textureAngle)))
                {
                    if (IntersectPixels(this.GetBoundingBox().CollisionRectangle, tmpTexData1, new Rectangle((int)texturePosition.X, (int)texturePosition.Y, texture.Width, texture.Height), tmpTexData2))
                        return true;
                    else
                        return false;
                }
                else
                    return false;
            }
            else // One or both of the sprites have been rotated, do all calculations
            {
                // The matrices used to calculate the transformations 
                Matrix sprite1Transformations; // This instance of the sprite
                Matrix sprite2Transformations; // The sprite to check against

                sprite1Transformations =
                    Matrix.CreateTranslation(new Vector3(-_origin, 1)) *
                    Matrix.CreateScale(_scale.X, _scale.Y, 1) *
                    Matrix.CreateRotationZ(_angle) *
                    Matrix.CreateTranslation(new Vector3(_position, 1));

                sprite2Transformations =
                    Matrix.CreateTranslation(new Vector3(new Vector2(texture.Width / -2, texture.Height / -2), 1)) *
                    Matrix.CreateScale(textureScale.X, textureScale.Y, 1) *
                    Matrix.CreateRotationZ(textureAngle) *
                    Matrix.CreateTranslation(new Vector3(texturePosition, 1));

                // Make sure that there is at least a collision between the two sprites, because pixel collision is very expensive
                if (this.GetBoundingBox().Intersects(this.GetBoundingBox()))
                {
                    if (IntersectPixels(sprite1Transformations, this.Width, this.Height, tmpTexData1, sprite2Transformations, texture.Width, texture.Height, tmpTexData2))
                        return true;
                    else
                        return false;
                }
                else
                    return false;

            }
        }

        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels
        /// between two sprites.
        /// </summary>
        /// <param name="rectangleA">Bounding rectangle of the first sprite</param>
        /// <param name="dataA">Pixel data of the first sprite</param>
        /// <param name="rectangleB">Bouding rectangle of the second sprite</param>
        /// <param name="dataB">Pixel data of the second sprite</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
                                           Rectangle rectangleB, Color[] dataB)
        {
            // Find the bounds of the rectangle intersection
            int top = Math.Max(rectangleA.Top, rectangleB.Top);
            int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
            int left = Math.Max(rectangleA.Left, rectangleB.Left);
            int right = Math.Min(rectangleA.Right, rectangleB.Right);

            // Check every point within the intersection bounds
            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = dataA[(x - rectangleA.Left) +
                                         (y - rectangleA.Top) * rectangleA.Width];
                    Color colorB = dataB[(x - rectangleB.Left) +
                                         (y - rectangleB.Top) * rectangleB.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }

            // No intersection found
            return false;
        }

        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels between two transformed
        /// sprites.
        /// </summary>
        /// <param name="transformA">World transform of the first sprite.</param>
        /// <param name="widthA">Width of the first sprite's texture.</param>
        /// <param name="heightA">Height of the first sprite's texture.</param>
        /// <param name="dataA">Pixel color data of the first sprite.</param>
        /// <param name="transformB">World transform of the second sprite.</param>
        /// <param name="widthB">Width of the second sprite's texture.</param>
        /// <param name="heightB">Height of the second sprite's texture.</param>
        /// <param name="dataB">Pixel color data of the second sprite.</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        static bool IntersectPixels(
                            Matrix transformA, int widthA, int heightA, Color[] dataA,
                            Matrix transformB, int widthB, int heightB, Color[] dataB)
        {
            // Calculate a matrix which transforms from A's local space into
            // world space and then into B's local space
            Matrix transformAToB = transformA * Matrix.Invert(transformB);

            // When a point moves in A's local space, it moves in B's local space with a
            // fixed direction and distance proportional to the movement in A.
            // This algorithm steps through A one pixel at a time along A's X and Y axes
            // Calculate the analogous steps in B:
            Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
            Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);

            // Calculate the top left corner of A in B's local space
            // This variable will be reused to keep track of the start of each row
            Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);

            // For each row of pixels in A
            for (int yA = 0; yA < heightA; yA++)
            {
                // Start at the beginning of the row
                Vector2 posInB = yPosInB;

                // For each pixel in this row
                for (int xA = 0; xA < widthA; xA++)
                {
                    // Round to the nearest pixel
                    int xB = (int)Math.Round(posInB.X);
                    int yB = (int)Math.Round(posInB.Y);

                    // If the pixel lies within the bounds of B
                    if (0 <= xB && xB < widthB &&
                        0 <= yB && yB < heightB)
                    {
                        // Get the colors of the overlapping pixels
                        Color colorA = dataA[xA + yA * widthA];
                        Color colorB = dataB[xB + yB * widthB];

                        // If both pixels are not completely transparent,
                        if (colorA.A != 0 && colorB.A != 0)
                        {
                            // then an intersection has been found
                            return true;
                        }
                    }

                    // Move to the next pixel in the row
                    posInB += stepX;
                }

                // Move to the next row
                yPosInB += stepY;
            }

            // No intersection found
            return false;
        }

        public Texture2D GetTexture()
        {
            return _sprite;
        }

        public RotatedRectangle GetBoundingBox()
        {
            return new RotatedRectangle(new Rectangle((int)(_position.X - _origin.X), (int)(_position.Y - _origin.Y), _sprite.Width, _sprite.Height), _angle); 
        }
        
        #endregion

        #region Overridden functions

        protected override void Load()
        {
            _sprite = ParentEngine.Content.Load<Texture2D>(_spritePath);
            _origin.X = _sprite.Width / 2;
            _origin.Y = _sprite.Height / 2;

            base.Load();
        }

        public override void Draw()
        {
            if (_hasParallax)
            {
                ParentEngine.BeginDefaultSpritebatch(_parallax);
            }
            else
            {
                ParentEngine.BeginDefaultSpritebatch();
            }
                ParentEngine.SpriteBatch.Draw(_sprite, _position, null, Color.White, _angle, _origin, _scale, SpriteEffects.None, 0);
            ParentEngine.SpriteBatch.End();

            base.Draw();
        }
        
        #endregion
    }
}
